<?php

namespace App\Admin\Filters;

use App\Admin\Models\SystemModel;
use App\Admin\Resources\SystemModelResource;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;

abstract class Filter
{
    protected $request;

    /**
     * @var Builder
     */
    protected $builder;

    protected $filters = [];

    protected $simpleFilters = [];

    protected $modelName;

    public static $filterRuleMap = [
        [
            "equal",
            "?"
        ],
        [
            "not equal",
            "?"
        ],
        [
            "lt",
            "?"
        ],
        [
            "elt",
            "?"
        ],
        [
            "gt",
            "?"
        ],
        [
            "egt",
            "?"
        ],
        [
            "like",
            "%?%"
        ],
        [
            "not like",
            "%?%"
        ],
        [
            "begin",
            "?%"
        ],
        [
            "not begin",
            "?%"
        ],
        [
            "end",
            "%?"
        ],
        [
            "not end",
            "%?"
        ],
        [
            "null"
        ],
        [
            "not null"
        ],
        [
            "empty"
        ],
        [
            "not empty"
        ],
        [
            "between",
            "?",
            "?"
        ],
        [
            "not between",
            "?",
            "?"
        ],
        [
            "in",
            "?"
        ],
        [
            "not in",
            "?"
        ],
        [
            "find_in_set",
            "?"
        ],
    ];

    public function __construct(Request $request, String $modelName = null)
    {
        $this->request = $request;
        $this->modelName = $modelName;
        if ($this->modelName) {
            $this->simpleFilters = SystemModel::searchMapFields($this->modelName);
            $this->filters = SystemModel::searchMapFields($this->modelName, true);

        }
        $this->formatSimpleFilters();
    }

    /**
     * 把 filters 中没有指定过滤类型的, 自动改为 'equal'
     */
    protected function formatFilters()
    {
        $t = [];
        foreach ($this->filters as $field => $op) {
            if (is_int($field)) {
                $t[$op] = 'equal';
            } else {
                $t[$field] = $op;
            }
        }
        $this->filters = $t;
    }

    /**
     * 把 simpleFilters 中没有指定过滤类型的, 自动改为 'equal'
     */
    protected function formatSimpleFilters()
    {
        $t = [];
        foreach ($this->simpleFilters as $field => $op) {
            if (is_int($field)) {
                $t[$op] = 'equal';
            } else {
                $t[$field] = $op;
            }
        }
        $this->simpleFilters = $t;
    }

    /**
     * 应用过滤
     *
     * @param $builder
     *
     * @return mixed
     */
    public function apply($builder)
    {
        $this->builder = $builder;
        foreach ($this->getFilters() as $filter => $value) {
            if (is_null($value)) {
                continue;
            }
            if ($op = $this->simpleFilters[$filter] ?? null) {
                $this->applyFilter($filter, $op, $value);
            } else { // 其他自定义过滤
                $method = Str::camel($filter);
                if (method_exists($this, $method)) {
                    $this->$method($value);
                }
            }
        }
        return $this->builder;
    }

    /**
     * 应用过滤器
     *
     * @param string $filter 过滤字段
     * @param string|array $op 操作
     * @param mixed $value 请求中对应的值
     */
    protected function applyFilter($filter, $op, $value, $isVirtual=false)
    {
        if (is_array($op)) {
            $args = array_slice($op, 1);
            $op = $op[0];
        }

        // 如果是虚拟字段，使用虚拟字段列表默认值作为查询
        if ($isVirtual) {
            $fieldValue = SystemModel::getVirtualFieldValue($this->modelName, $filter);
            if ($fieldValue) {
                $filter = DB::raw($fieldValue);  
            } else {
                $filter = null;
            }
        }

        if ($filter) {
            switch ($op) {
                case 'equal':
                    $this->builder->where($filter, $value);
                    break;
                case 'not equal':
                    $this->builder->where($filter, '<>', $value);
                    break;
                case 'lt':
                    $this->builder->where($filter, '<', $value);
                    break;
                case 'elt':
                    $this->builder->where($filter, '<=', $value);
                    break;
                case 'gt':
                    $this->builder->where($filter, '>', $value);
                    break;
                case 'egt':
                    $this->builder->where($filter, '>=', $value);
                    break;
                case 'like':
                case 'begin':
                case 'end':
                    $this->builder->where($filter, 'like', str_replace('?', $value, $args[0]));
                    break;
                case 'not like':
                case 'not like':
                case 'not end':
                    $this->builder->where($filter, 'not like', str_replace('?', $value, $args[0]));
                    break;
                case 'null':
                    if ($value=='true') {
                        $this->builder->whereNull($filter);
                    } elseif ($value=='false') {
                        $this->builder->whereNotNull($filter);
                    }
                    break;
                case 'not null':
                    if ($value=='true') {
                        $this->builder->whereNotNull($filter);
                    } elseif ($value=='false') {
                        $this->builder->whereNull($filter);
                    }
                    break;
                case 'empty':
                    if ($value=='true') {
                        $this->builder->where($filter, '=', '');
                    } elseif ($value=='false') {
                        $this->builder->where($filter, '<>', '');
                    }
                    break;
                case 'not empty':
                    if ($value=='true') {
                        $this->builder->where($filter, '<>', '');
                    } elseif ($value=='false') {
                        $this->builder->where($filter, '=', '');
                    }
                    break;
                case 'between':
                    $value = stringToArr($value, ',');
                    $this->builder->whereBetween($filter, [$value[0], $value[1]]);
                    break;
                case 'not between':
                    $value = stringToArr($value, ',');
                    $this->builder->whereNotBetween($filter, [$value[0], $value[1]]);
                    break;
                case 'in':
                    $value = stringToArr($value, ',');
                    $this->builder->whereIn($filter, $value);
                    break;
                case 'not in':
                    $value = stringToArr($value, ',');
                    $this->builder->whereNotIn($filter, $value);
                    break;
                case 'find_in_set':
                    $value = stringToArr($value, ',');
                    $this->builder->whereRaw("FIND_IN_SET(?,{$filter})", $value);
                    break;
                default:
                    // do nothing
            }
        }
    }

    public function getFilters()
    {
        return $this->request->only(array_merge($this->filters, array_keys($this->simpleFilters)));
    }

    /**
     * 只保留特定的过滤字段
     *
     * @param array|string $only
     *
     * @return $this
     */
    public function only($only)
    {
        if (is_string($only)) {
            $only = [$only];
        }

        $this->filters = array_intersect($this->filters, $only);
        $this->simpleFilters = array_intersect_key($this->simpleFilters, array_flip($only));

        return $this;
    }
}
